#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
JSON文件处理器

该模块提供了JSON文件的读写和处理功能。

创建时间: 2023-07-12
作者: Python Tools Team
"""

import json
from typing import Optional, Union, Dict, Any, List, Callable
from pathlib import Path

from modules.logger import LogManager
from modules.exception import exception_handler, FileException
from modules.fileio.file_manager import FileManager


class JSONFileHandler:
    """
    JSON文件处理器
    
    该类提供了JSON文件的读写和处理功能，支持多种格式和选项。
    """
    
    def __init__(self, file_manager: Optional[FileManager] = None, logger=None):
        """
        初始化JSON文件处理器
        
        Args:
            file_manager: 文件管理器实例，如果为None则创建新实例
            logger: 日志记录器，如果为None则使用默认日志记录器
        """
        self.file_manager = file_manager or FileManager()
        self.logger = logger or LogManager.get_both_logger(name="json_handler")
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def read(self, file_path: Union[str, Path], encoding: str = 'utf-8') -> Any:
        """
        读取JSON文件
        
        Args:
            file_path: JSON文件路径
            encoding: 文件编码，默认为utf-8
        
        Returns:
            Any: 解析后的JSON数据
        
        Raises:
            FileException: 文件不存在或读取失败时抛出
            json.JSONDecodeError: JSON解析失败时抛出
        
        Examples:
            >>> json_handler = JSONFileHandler()
            >>> data = json_handler.read('example.json')
            >>> print(data)
        """
        file_path = Path(file_path)
        if not file_path.exists():
            raise FileException(f"文件不存在: {file_path}")
        
        content = self.file_manager.read_text(file_path, encoding=encoding)
        
        try:
            data = json.loads(content)
        except json.JSONDecodeError as e:
            self.logger.error(f"JSON解析失败: {e}")
            raise
        
        self.logger.debug(f"已读取JSON文件: {file_path}")
        return data
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def write(self, file_path: Union[str, Path], data: Any, encoding: str = 'utf-8', 
              indent: Optional[int] = 4, ensure_ascii: bool = False, 
              sort_keys: bool = False, create_dirs: bool = True) -> bool:
        """
        写入数据到JSON文件
        
        Args:
            file_path: JSON文件路径
            data: 要写入的数据
            encoding: 文件编码，默认为utf-8
            indent: 缩进空格数，默认为4，None表示不缩进
            ensure_ascii: 是否确保ASCII编码，默认为False
            sort_keys: 是否按键排序，默认为False
            create_dirs: 如果父目录不存在，是否创建，默认为True
        
        Returns:
            bool: 写入成功返回True
        
        Raises:
            FileException: 写入失败时抛出
            TypeError: 数据不可序列化时抛出
        
        Examples:
            >>> json_handler = JSONFileHandler()
            >>> data = {'name': 'Alice', 'age': 30, 'skills': ['Python', 'Java']}
            >>> json_handler.write('example.json', data)
        """
        file_path = Path(file_path)
        
        try:
            json_str = json.dumps(data, indent=indent, ensure_ascii=ensure_ascii, 
                                 sort_keys=sort_keys)
        except TypeError as e:
            self.logger.error(f"JSON序列化失败: {e}")
            raise
        
        self.file_manager.write_text(file_path, json_str, encoding=encoding, 
                                    create_dirs=create_dirs)
        
        self.logger.debug(f"已写入JSON文件: {file_path}")
        return True
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def update(self, file_path: Union[str, Path], data: Dict[str, Any], 
               encoding: str = 'utf-8', indent: Optional[int] = 4, 
               ensure_ascii: bool = False, sort_keys: bool = False, 
               create_if_not_exists: bool = True, create_dirs: bool = True) -> bool:
        """
        更新JSON文件中的数据
        
        如果文件存在，读取现有数据并更新；如果不存在，则创建新文件。
        
        Args:
            file_path: JSON文件路径
            data: 要更新的数据字典
            encoding: 文件编码，默认为utf-8
            indent: 缩进空格数，默认为4，None表示不缩进
            ensure_ascii: 是否确保ASCII编码，默认为False
            sort_keys: 是否按键排序，默认为False
            create_if_not_exists: 如果文件不存在，是否创建，默认为True
            create_dirs: 如果父目录不存在，是否创建，默认为True
        
        Returns:
            bool: 更新成功返回True
        
        Raises:
            FileException: 更新失败时抛出
            TypeError: 数据不可序列化时抛出
        
        Examples:
            >>> json_handler = JSONFileHandler()
            >>> data = {'age': 31, 'skills': ['Python', 'Java', 'C++']}
            >>> json_handler.update('example.json', data)
        """
        file_path = Path(file_path)
        
        # 如果文件存在，读取现有数据并更新
        if file_path.exists():
            try:
                existing_data = self.read(file_path, encoding=encoding)
                
                # 确保现有数据是字典
                if not isinstance(existing_data, dict):
                    existing_data = {}
                
                # 更新数据
                existing_data.update(data)
                data_to_write = existing_data
            except Exception as e:
                self.logger.warning(f"读取现有JSON文件失败，将创建新文件: {e}")
                data_to_write = data
        else:
            # 文件不存在
            if not create_if_not_exists:
                raise FileException(f"文件不存在: {file_path}")
            data_to_write = data
        
        # 写入更新后的数据
        return self.write(file_path, data_to_write, encoding, indent, 
                         ensure_ascii, sort_keys, create_dirs)
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def merge(self, file_path: Union[str, Path], data: Any, 
              encoding: str = 'utf-8', indent: Optional[int] = 4, 
              ensure_ascii: bool = False, sort_keys: bool = False, 
              create_if_not_exists: bool = True, create_dirs: bool = True) -> bool:
        """
        合并数据到JSON文件
        
        与update不同，merge可以处理嵌套字典的合并。
        
        Args:
            file_path: JSON文件路径
            data: 要合并的数据
            encoding: 文件编码，默认为utf-8
            indent: 缩进空格数，默认为4，None表示不缩进
            ensure_ascii: 是否确保ASCII编码，默认为False
            sort_keys: 是否按键排序，默认为False
            create_if_not_exists: 如果文件不存在，是否创建，默认为True
            create_dirs: 如果父目录不存在，是否创建，默认为True
        
        Returns:
            bool: 合并成功返回True
        
        Raises:
            FileException: 合并失败时抛出
            TypeError: 数据不可序列化时抛出
        
        Examples:
            >>> json_handler = JSONFileHandler()
            >>> data = {'user': {'age': 31, 'skills': ['Python', 'Java', 'C++']}}
            >>> json_handler.merge('example.json', data)
        """
        file_path = Path(file_path)
        
        # 如果文件存在，读取现有数据并合并
        if file_path.exists():
            try:
                existing_data = self.read(file_path, encoding=encoding)
                
                # 合并数据
                merged_data = self._deep_merge(existing_data, data)
                data_to_write = merged_data
            except Exception as e:
                self.logger.warning(f"读取现有JSON文件失败，将创建新文件: {e}")
                data_to_write = data
        else:
            # 文件不存在
            if not create_if_not_exists:
                raise FileException(f"文件不存在: {file_path}")
            data_to_write = data
        
        # 写入合并后的数据
        return self.write(file_path, data_to_write, encoding, indent, 
                         ensure_ascii, sort_keys, create_dirs)
    
    def _deep_merge(self, source: Any, destination: Any) -> Any:
        """
        深度合并两个数据结构
        
        Args:
            source: 源数据
            destination: 目标数据
        
        Returns:
            Any: 合并后的数据
        """
        if isinstance(source, dict) and isinstance(destination, dict):
            result = source.copy()
            for key, value in destination.items():
                if key in result and isinstance(result[key], dict) and isinstance(value, dict):
                    result[key] = self._deep_merge(result[key], value)
                elif key in result and isinstance(result[key], list) and isinstance(value, list):
                    result[key] = result[key] + [item for item in value if item not in result[key]]
                else:
                    result[key] = value
            return result
        elif isinstance(source, list) and isinstance(destination, list):
            return source + [item for item in destination if item not in source]
        else:
            return destination
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def get_value(self, file_path: Union[str, Path], key_path: str, 
                 default: Any = None, encoding: str = 'utf-8') -> Any:
        """
        获取JSON文件中指定路径的值
        
        Args:
            file_path: JSON文件路径
            key_path: 键路径，使用点号分隔，如'user.profile.name'
            default: 如果键不存在，返回的默认值
            encoding: 文件编码，默认为utf-8
        
        Returns:
            Any: 指定路径的值，如果路径不存在则返回默认值
        
        Raises:
            FileException: 文件不存在或读取失败时抛出
        
        Examples:
            >>> json_handler = JSONFileHandler()
            >>> name = json_handler.get_value('example.json', 'user.profile.name', default='Unknown')
            >>> print(name)
        """
        data = self.read(file_path, encoding=encoding)
        
        keys = key_path.split('.')
        current = data
        
        for key in keys:
            if isinstance(current, dict) and key in current:
                current = current[key]
            else:
                return default
        
        return current
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def set_value(self, file_path: Union[str, Path], key_path: str, value: Any, 
                 encoding: str = 'utf-8', indent: Optional[int] = 4, 
                 ensure_ascii: bool = False, sort_keys: bool = False, 
                 create_if_not_exists: bool = True, create_dirs: bool = True) -> bool:
        """
        设置JSON文件中指定路径的值
        
        Args:
            file_path: JSON文件路径
            key_path: 键路径，使用点号分隔，如'user.profile.name'
            value: 要设置的值
            encoding: 文件编码，默认为utf-8
            indent: 缩进空格数，默认为4，None表示不缩进
            ensure_ascii: 是否确保ASCII编码，默认为False
            sort_keys: 是否按键排序，默认为False
            create_if_not_exists: 如果文件不存在，是否创建，默认为True
            create_dirs: 如果父目录不存在，是否创建，默认为True
        
        Returns:
            bool: 设置成功返回True
        
        Raises:
            FileException: 设置失败时抛出
        
        Examples:
            >>> json_handler = JSONFileHandler()
            >>> json_handler.set_value('example.json', 'user.profile.name', 'Alice')
        """
        file_path = Path(file_path)
        
        # 如果文件存在，读取现有数据
        if file_path.exists():
            try:
                data = self.read(file_path, encoding=encoding)
                if not isinstance(data, dict):
                    data = {}
            except Exception as e:
                self.logger.warning(f"读取现有JSON文件失败，将创建新文件: {e}")
                data = {}
        else:
            # 文件不存在
            if not create_if_not_exists:
                raise FileException(f"文件不存在: {file_path}")
            data = {}
        
        # 设置值
        keys = key_path.split('.')
        current = data
        
        # 遍历键路径，创建必要的嵌套字典
        for i, key in enumerate(keys[:-1]):
            if key not in current or not isinstance(current[key], dict):
                current[key] = {}
            current = current[key]
        
        # 设置最终值
        current[keys[-1]] = value
        
        # 写入更新后的数据
        return self.write(file_path, data, encoding, indent, 
                         ensure_ascii, sort_keys, create_dirs)
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def delete_key(self, file_path: Union[str, Path], key_path: str, 
                  encoding: str = 'utf-8', indent: Optional[int] = 4, 
                  ensure_ascii: bool = False, sort_keys: bool = False) -> bool:
        """
        删除JSON文件中指定路径的键
        
        Args:
            file_path: JSON文件路径
            key_path: 键路径，使用点号分隔，如'user.profile.name'
            encoding: 文件编码，默认为utf-8
            indent: 缩进空格数，默认为4，None表示不缩进
            ensure_ascii: 是否确保ASCII编码，默认为False
            sort_keys: 是否按键排序，默认为False
        
        Returns:
            bool: 删除成功返回True，键不存在也返回True
        
        Raises:
            FileException: 文件不存在或删除失败时抛出
        
        Examples:
            >>> json_handler = JSONFileHandler()
            >>> json_handler.delete_key('example.json', 'user.profile.name')
        """
        file_path = Path(file_path)
        
        if not file_path.exists():
            raise FileException(f"文件不存在: {file_path}")
        
        data = self.read(file_path, encoding=encoding)
        
        keys = key_path.split('.')
        current = data
        
        # 遍历键路径
        for i, key in enumerate(keys[:-1]):
            if isinstance(current, dict) and key in current:
                current = current[key]
            else:
                # 键路径不存在，视为删除成功
                return True
        
        # 删除最终键
        if isinstance(current, dict) and keys[-1] in current:
            del current[keys[-1]]
        
        # 写入更新后的数据
        return self.write(file_path, data, encoding, indent, ensure_ascii, sort_keys)
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def filter_keys(self, data: Dict[str, Any], include_keys: Optional[List[str]] = None, 
                   exclude_keys: Optional[List[str]] = None) -> Dict[str, Any]:
        """
        筛选字典中的键
        
        Args:
            data: 要筛选的字典
            include_keys: 要包含的键列表，如果为None则包含所有键
            exclude_keys: 要排除的键列表，如果为None则不排除任何键
        
        Returns:
            Dict[str, Any]: 筛选后的字典
        
        Examples:
            >>> json_handler = JSONFileHandler()
            >>> data = {'name': 'Alice', 'age': 30, 'email': 'alice@example.com'}
            >>> filtered = json_handler.filter_keys(data, include_keys=['name', 'age'])
            >>> print(filtered)
            >>> # 输出: {'name': 'Alice', 'age': 30}
        """
        if not isinstance(data, dict):
            return data
        
        result = {}
        
        for key, value in data.items():
            # 检查是否包含该键
            if include_keys is not None and key not in include_keys:
                continue
            
            # 检查是否排除该键
            if exclude_keys is not None and key in exclude_keys:
                continue
            
            # 如果值是字典，递归筛选
            if isinstance(value, dict):
                result[key] = self.filter_keys(value, include_keys, exclude_keys)
            # 如果值是列表且包含字典，递归筛选列表中的字典
            elif isinstance(value, list):
                result[key] = [self.filter_keys(item, include_keys, exclude_keys) 
                              if isinstance(item, dict) else item for item in value]
            else:
                result[key] = value
        
        return result
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def flatten(self, data: Dict[str, Any], separator: str = '.') -> Dict[str, Any]:
        """
        将嵌套字典扁平化
        
        Args:
            data: 要扁平化的嵌套字典
            separator: 键路径分隔符，默认为点号
        
        Returns:
            Dict[str, Any]: 扁平化后的字典
        
        Examples:
            >>> json_handler = JSONFileHandler()
            >>> data = {'user': {'profile': {'name': 'Alice', 'age': 30}}}
            >>> flattened = json_handler.flatten(data)
            >>> print(flattened)
            >>> # 输出: {'user.profile.name': 'Alice', 'user.profile.age': 30}
        """
        result = {}
        
        def _flatten(current, path=''):
            if isinstance(current, dict):
                for key, value in current.items():
                    new_path = f"{path}{separator}{key}" if path else key
                    _flatten(value, new_path)
            elif isinstance(current, list):
                for i, item in enumerate(current):
                    new_path = f"{path}{separator}{i}" if path else str(i)
                    _flatten(item, new_path)
            else:
                result[path] = current
        
        _flatten(data)
        return result
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def unflatten(self, data: Dict[str, Any], separator: str = '.') -> Dict[str, Any]:
        """
        将扁平化字典还原为嵌套字典
        
        Args:
            data: 要还原的扁平化字典
            separator: 键路径分隔符，默认为点号
        
        Returns:
            Dict[str, Any]: 嵌套字典
        
        Examples:
            >>> json_handler = JSONFileHandler()
            >>> data = {'user.profile.name': 'Alice', 'user.profile.age': 30}
            >>> nested = json_handler.unflatten(data)
            >>> print(nested)
            >>> # 输出: {'user': {'profile': {'name': 'Alice', 'age': 30}}}
        """
        result = {}
        
        for key, value in data.items():
            parts = key.split(separator)
            current = result
            
            for i, part in enumerate(parts[:-1]):
                # 尝试将数字索引转换为整数
                try:
                    part = int(part)
                except ValueError:
                    pass
                
                # 如果是整数索引，确保当前是列表
                if isinstance(part, int):
                    if not isinstance(current, list):
                        current = []
                    # 确保列表长度足够
                    while len(current) <= part:
                        current.append({} if i < len(parts) - 2 else None)
                    
                    if i < len(parts) - 2:
                        if not isinstance(current[part], dict):
                            current[part] = {}
                        current = current[part]
                    else:
                        current[part] = value
                else:
                    # 常规字典键
                    if part not in current:
                        current[part] = {}
                    current = current[part]
            
            # 设置最终值
            last_part = parts[-1]
            try:
                last_part = int(last_part)
            except ValueError:
                pass
            
            current[last_part] = value
        
        return result